package com.share.tools.ldap;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;

/**
 * @author caifan
 * @date 2019/10/20
 */
public class Ldap2Java {

    private static final String BASE_DN = "dc=my,dc=com";
    private static final String USER_DN_BASE = "ou=users,dc=my,dc=com";
    private static final String GROUP_DN_BASE = "ou=group,dc=my,dc=com";

    public static void main(String[] args) throws Exception {
        LdapContext connectLdap = connectLdap();
        List<String> groups = new ArrayList<String>();
        Ldap2Java ldapTest = new Ldap2Java();
        ldapTest.addUser("aaa", "aabc", "1", groups);
		/*getMaxId(connectLdap);
		 SearchControls searchCtls = new SearchControls();
        searchCtls.setSearchScope(SearchControls.OBJECT_SCOPE);
		NamingEnumeration<SearchResult> search = connectLdap.search("cn=test111,ou=group,dc=my,dc=com", "(objectClass=posixGroup)", null, searchCtls);
//		NamingEnumeration<SearchResult> search = connectLdap.search("cn=aaa,ou=users,dc=my,dc=com", "(objectClass=posixAccount)", null,searchCtls);
		while (search.hasMore()) {
			SearchResult result = search.next();
			//System.out.println(result.getAttributes().get("memberUid").get(2));//.get("description")
			System.out.println(result.getAttributes().get("gidnumber").get());//.get("description")
			System.out.println(result.getAttributes().get("objectClass"));
			System.out.println(result.getName());
		}
		getAllGroups(connectLdap);*/
    }

    public static void getAllGroups(LdapContext context) throws Exception {
        SearchControls searchCtls = new SearchControls();
        searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);

        //SHIRO-115 - prevent potential code injection:
        String searchFilter = "(&(objectClass=posixGroup))";
        NamingEnumeration answer = context.search("ou=group,dc=my,dc=com", searchFilter, null, searchCtls);

        while (answer.hasMoreElements()) {
            SearchResult searchResult = (SearchResult) answer.next();

            String group = searchResult.getName().substring(3);

            //获取description
            String groupName = "";
            Attribute groupNameAttr = searchResult.getAttributes().get("description");
            System.out.println(group + "::" + groupNameAttr + "::" + searchResult.getAttributes().get("memberUid"));

            /**
             * 获取所有memberUid
             */
            List<String> userIds = new ArrayList<>();
            Attribute memberUidAttr = searchResult.getAttributes().get("memberUid");
            if (memberUidAttr != null) {
                NamingEnumeration memberUid = memberUidAttr.getAll();
                while (memberUid.hasMore()) {
                    String userId = (String) memberUid.next();
                    userIds.add(userId);
                }
            }

        }

    }

    /**
     * 获取最大id
     * @param context
     * @return
     * @throws Exception
     */
    public static Integer getMaxId(LdapContext context) throws Exception {
        SearchControls searchCtls = new SearchControls();
        searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
        List<Integer> idList = new ArrayList<>();

        //NamingEnumeration<SearchResult> search = context.search("ou=group,dc=my,dc=com", "(objectClass=posixGroup)", null,searchCtls);
        NamingEnumeration<SearchResult> search = context.search("ou=users,dc=my,dc=com", "(objectClass=posixAccount)", null, searchCtls);
        while (search.hasMore()) {
            SearchResult result = search.next();
            //System.out.println(result.getAttributes().get("memberUid").get(2));//.get("description")
            //System.out.println(result.getAttributes().get("gidnumber").get());//.get("description")

            idList.add(Integer.parseInt(result.getAttributes().get("uidnumber").get().toString()));
            //System.out.println(result.getAttributes().get("uidnumber").get());
        }
        Collections.sort(idList);
        for (Integer id : idList) {
            System.out.println(id);
        }
        System.out.println("maxID:" + idList.get(idList.size() - 1));
        return idList.get(0);
    }

    public static LdapContext connectLdap() throws Exception {
        //连接Ldap服务器
        String ldapFactory = "com.sun.jndi.ldap.LdapCtxFactory";
        String ldapUrl = "ldap://host:389";// url
        String ldapAccount = "cn=admin,dc=my,dc=com";
        String ldapPwd = "test";
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY, ldapFactory);
        // LDAP server
        env.put(Context.PROVIDER_URL, ldapUrl);
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.SECURITY_PRINCIPAL, ldapAccount);
        env.put(Context.SECURITY_CREDENTIALS, ldapPwd);
        env.put("java.naming.referral", "follow");
        LdapContext ctxTDS = new InitialLdapContext(env, null);
        return ctxTDS;
    }


    public void addUser(String cn, String givenName, String password, List<String> group) throws NamingException {
        LdapContext ldapContext = null;
        try {
            ldapContext = connectLdap();
            Integer uidNumber = getMaxId(ldapContext, USER_DN_BASE) + 1;
            String md5 = generateMD5(password);
            Attributes attributes = new BasicAttributes();
            Attribute passwordAttribute = new BasicAttribute("userPassword", md5);
            Attribute objectClass = new BasicAttribute("objectClass", true);
            objectClass.add("inetOrgPerson");
            objectClass.add("posixAccount");
            objectClass.add("top");

            Attribute cnAttr = new BasicAttribute("cn", cn);
            Attribute givenNameAttr = new BasicAttribute("givenName", givenName);
            Attribute homeDirectoryAttr = new BasicAttribute("homeDirectory", "/home/users/" + cn);
            Attribute loginShellAttr = new BasicAttribute("loginShell", "/bin/sh");
            Attribute snAttr = new BasicAttribute("sn", cn);
            Attribute uidAttr = new BasicAttribute("uid", cn);
            Attribute uidNumberAttr = new BasicAttribute("uidNumber", uidNumber + "");

            SearchControls searchCtls = new SearchControls();
            searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
            if (group.size() > 0) {
                NamingEnumeration<SearchResult> searchGroup = ldapContext.search("cn=" + group.get(0) + "," + GROUP_DN_BASE, "(objectClass=posixGroup)", null, searchCtls);
                while (searchGroup.hasMore()) {
                    String gid = searchGroup.next().getAttributes().get("gidNumber").get().toString();
                    Attribute gidAttr = new BasicAttribute("gidNumber", gid);
                    attributes.put(gidAttr);
                }
                //添加用户到组
                group.forEach(groupName -> {
                    try {
                        moveUser2Group(cn, groupName, 0);
                    } catch (Exception e) {
                    }
                });
            }
            Attribute gidAttr = new BasicAttribute("gidNumber", "512");
            attributes.put(gidAttr);
            attributes.put(cnAttr);
            attributes.put(givenNameAttr);
            attributes.put(passwordAttribute);
            attributes.put(objectClass);
            attributes.put(loginShellAttr);
            attributes.put(snAttr);
            attributes.put(homeDirectoryAttr);
            attributes.put(uidAttr);
            attributes.put(uidNumberAttr);

            ldapContext.createSubcontext("cn=" + cn + "," + USER_DN_BASE, attributes);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (ldapContext != null) {
                ldapContext.close();
            }
        }

    }

    /**
     * 修改用户
     *
     * @param cn
     * @param givenName
     * @param addGroup
     * @param subGroup
     */
    public void modifyUser(String cn, String givenName, List<String> addGroup, List<String> subGroup) {
        LdapContext ldapContext = null;
        try {
            ldapContext = connectLdap();
            Attributes attributes = new BasicAttributes();
            SearchControls searchControls = new SearchControls();
            searchControls.setSearchScope(SearchControls.OBJECT_SCOPE);
            NamingEnumeration<SearchResult> search = ldapContext.search("cn=" + cn + "," + USER_DN_BASE, "(objectClass=posixAccount)", null, searchControls);
            while (search.hasMore()) {
                SearchResult searchResult = search.next();
                Attributes beforeAttrs = searchResult.getAttributes();
                Attribute givenNameAttrs = beforeAttrs.get("givenName");
                if (givenNameAttrs != null && givenNameAttrs.get() != null && !givenNameAttrs.get().toString().trim().equals(givenName.trim())) {
                    givenNameAttrs.clear();
                    givenNameAttrs.add(givenName);
                    attributes.put(givenNameAttrs);
                } else {
                    givenNameAttrs = new BasicAttribute("givenName", givenName);
                    attributes.put(givenNameAttrs);
                }
                Attribute uidAttrs = beforeAttrs.get("uid");

                if (addGroup != null && !addGroup.isEmpty()) {
                    if (addGroup.get(0).trim().length() > 0) {
                        NamingEnumeration<SearchResult> searchResults = ldapContext.search("cn=" + addGroup.get(0) + "," + GROUP_DN_BASE, "(objectClass=posixGroup)", null, searchControls);
                        Attribute gidAttribute = null;
                        while (searchResults.hasMore()) {
                            SearchResult sr = searchResults.next();
                            gidAttribute = sr.getAttributes().get("gidNumber");
                            attributes.put(new BasicAttribute("gidNumber", gidAttribute.get()));
                        }
                        addGroup.forEach(add -> {
                            try {
                                if (add.trim().length() > 0) {
                                    moveUser2Group(cn, add, 0);
                                }
                            } catch (Exception e) {
                            }
                        });
                    }
                }
                if (subGroup != null && subGroup.size() > 0) {
                    subGroup.forEach(sub -> {
                        try {
                            if (sub.trim().length() > 0) {
                                moveUser2Group(cn, sub, 1);
                            }
                        } catch (Exception e) {
                        }
                    });
                }

                ldapContext.modifyAttributes("cn=" + cn + "," + USER_DN_BASE, DirContext.REPLACE_ATTRIBUTE, attributes);
            }

        } catch (Exception e) {
        } finally {
            if (ldapContext != null) {
                try {
                    ldapContext.close();
                } catch (NamingException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }

    private void modifyList(Attribute attrs, List<String> addValue, List<String> subValue) {
        if (attrs != null) {
            if (addValue != null && addValue.size() > 0) {
                for (String add : addValue) {
                    if (!attrs.contains(add)) {
                        attrs.add(add);
                    }
                }
            }
            if (subValue != null && subValue.size() > 0) {
                for (String sub : subValue) {
                    if (attrs.contains(sub)) {
                        attrs.remove(sub);
                    }
                }
            }
        }
    }

    public void deleteUser(String username) throws Exception {
        LdapContext context = null;
        try {
            context = connectLdap();
            context.destroySubcontext("cn=" + username + "," + USER_DN_BASE);
        } catch (NamingException e) {
        } finally {
            if (context != null) {
                context.close();
                ;
            }
        }

    }

    /**
     * 添加用户到组
     *
     * @param userId
     * @param groupName
     * @param type 0添加 1移除
     * @throws Exception
     */
    private void moveUser2Group(String userId, String groupName, Integer type) throws Exception {
        LdapContext context = connectLdap();
        String searchFilter = "(&(objectClass=posixGroup))";
        SearchControls searchCtrl = new SearchControls();
        searchCtrl.setSearchScope(SearchControls.OBJECT_SCOPE);
        NamingEnumeration answer = context.search("cn=" + groupName + "," + GROUP_DN_BASE, searchFilter, null, searchCtrl);
        SearchResult searchResult;
        while (answer.hasMore()) {
            searchResult = (SearchResult) answer.next();
            Attribute attribute = searchResult.getAttributes().get("memberUid");
            if (attribute == null) {
                attribute = new BasicAttribute("memberUid");
            }
            if (type == 0 && !attribute.contains(userId)) {
                attribute.add(attribute.size(), userId);
            } else if (type == 1 && attribute.contains(userId)) {
                attribute.remove(userId);
            }
            ModificationItem[] item = new ModificationItem[1];
            item[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attribute);
            context.modifyAttributes("cn=" + groupName + "," + GROUP_DN_BASE, item);
        }
    }

    private Integer getMaxId(LdapContext context, String dn) throws Exception {
        SearchControls searchCtls = new SearchControls();
        searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
        List<Integer> idList = new ArrayList<>();
        NamingEnumeration<SearchResult> search = null;
        if (GROUP_DN_BASE.equals(dn)) {
            search = context.search(GROUP_DN_BASE, "(objectClass=posixGroup)", null, searchCtls);
            while (search.hasMore()) {
                SearchResult result = search.next();
                idList.add(Integer.parseInt(result.getAttributes().get("gidnumber").get().toString()));
            }
        } else if (USER_DN_BASE.equals(dn)) {
            search = context.search(USER_DN_BASE, "(objectClass=posixAccount)", null, searchCtls);
            while (search.hasMore()) {
                SearchResult result = search.next();
                idList.add(Integer.parseInt(result.getAttributes().get("uidnumber").get().toString()));
            }
        }

        Collections.sort(idList);
        return idList.get(idList.size() - 1);
    }

    /**
     * md5加密
     * @param password
     * @return
     */
    public static String generateMD5(String password) {
        StringBuffer sb = new StringBuffer();
        try {
            MessageDigest md = MessageDigest.getInstance("md5");
            md.update(password.getBytes("utf-8"));
            byte[] hashCode = md.digest();

            for (byte b : hashCode) {
                sb.append(Character.forDigit(b >> 4 & 0xf, 16));
                sb.append(Character.forDigit(b & 0xf, 16));
            }
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return sb.toString();
    }
}
